﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace TheSnake
{
    public sealed class Snake : MovingObject, IMovable
    {
        //Constants
        public new const string CollisionGroupString = "snake";
        public const char Symbol = '\0';
        public const char HeadSymbol = '@';
        public const char TailSymbol = '*';


        //Singleton instance
        private static readonly Snake instance = new Snake(new MatrixCoords(2, 8), new MatrixCoords(0, 1), 6);

        public static Snake Instance
        {
            get
            {
                return instance;
            }
        }

        //Properties
        public SnakeBodyElement Head { get; set; }
        public Queue<SnakeBodyElement> Tail { get; set; }
        public Status SnakeStatus { get; set; }

        //Constructor
        private Snake(MatrixCoords topLeft, MatrixCoords speed, int length)
            : base(topLeft, new char[,] { { Symbol } }, speed)
        {
            SnakeStatus = new Status(new MatrixCoords(1, 55), 5, 0);

            this.Speed = speed;

            //create Tail
            //Could check for the length and throw exception!
            Tail = new Queue<SnakeBodyElement>(length - 1); //create the tail

            for (int i = 0; i < length - 1; i++)
            {
                Tail.Enqueue(new SnakeBodyElement(topLeft, new char[,] { { TailSymbol } }, speed)); //add snake body elements to the tail
                topLeft += speed;
            }

            //create Head
            Head = new SnakeBodyElement(topLeft, new char[,] { { HeadSymbol } }, speed);
        }

        //Methods
        public void MoveLeft()
        {
            if (this.Speed.Col != 1)
            {
                this.Speed = new MatrixCoords(0, -1);
                this.Head.UpdateSpeed(this.Speed);
            }
        }

        public void MoveRight()
        {
            if (this.Speed.Col != -1)
            {
                this.Speed = new MatrixCoords(0, 1);
                this.Head.UpdateSpeed(this.Speed);
            }
        }

        public void MoveUp()
        {
            if (this.Speed.Row != 1)
            {
                this.Speed = new MatrixCoords(-1, 0);
                this.Head.UpdateSpeed(this.Speed);
            }
        }

        public void MoveDown()
        {
            if (this.Speed.Row != -1)
            {
                this.Speed = new MatrixCoords(1, 0);
                this.Head.UpdateSpeed(this.Speed);
            }
        }

        public override void UpdatePosition()
        {
            Head.UpdatePosition();
            this.TopLeft = Head.TopLeft;

            // Previous tail first element
            SnakeBodyElement prevFirstTail = Tail.Last();

            // New tail first element
            SnakeBodyElement newFirstTail = new SnakeBodyElement(
                prevFirstTail.TopLeft + prevFirstTail.Speed,
                new char[,] { { TailSymbol } },
                this.Speed);

            Tail.Enqueue(newFirstTail);
            Tail.Dequeue();
        }

        public override bool CanCollideWith(string otherCollisionGroupString)
        {
            return otherCollisionGroupString == "block" || otherCollisionGroupString == "snake" ||
                   otherCollisionGroupString == "obsticle";
        }

        public override string GetCollisionGroupString()
        {
            return Snake.CollisionGroupString;
        }

        public override void RespondToCollision(CollisionData collisionData)
        {
            if (collisionData.hitObjectsCollisionGroupStrings[0] == "food")
            {
                this.SnakeStatus.Points += 1;
                this.SnakeStatus.Update();

                this.EnlargeSnake();
            }
            else if (collisionData.hitObjectsCollisionGroupStrings[0] == "bonus")
            {
                this.SnakeStatus.Lifes++;
                this.SnakeStatus.Update();
            }
            else if (collisionData.hitObjectsCollisionGroupStrings[0] == "block" ||
                collisionData.hitObjectsCollisionGroupStrings[0] == "obsticle" ||
                collisionData.hitObjectsCollisionGroupStrings[0] == "snake")
            {
                SnakeStatus.Lifes--;
                SnakeStatus.Update();
                if (SnakeStatus.Lifes == 0)
                {
                    this.SnakeDies();
                }
                else
                {
                    this.RestoreSnake();
                }
            }
        }

        private void EnlargeSnake()
        {
            // Previous tail first element
            SnakeBodyElement prevFirstTail = this.Tail.Last();

            // New tail first element
            SnakeBodyElement newFirstTail = new SnakeBodyElement(
                prevFirstTail.TopLeft,
                new char[,] { { TailSymbol } },
                this.Speed);

            this.Tail.Enqueue(newFirstTail);
        }

        private void RestoreSnake()
        {
            this.TopLeft = new MatrixCoords(2, 8);

            int oldLength = this.Tail.Count;
            int length = 5;

            MatrixCoords topLeft = this.topLeft;

            //Deque the tail
            for (int i = 0; i < oldLength - 1; i++)
            {
                Tail.Dequeue();
            }
            //Enque the tail
            for (int i = 0; i < length - 1; i++)
            {
                Tail.Enqueue(new SnakeBodyElement(topLeft, new char[,] { { TailSymbol } }, new MatrixCoords(0, 1))); //add snake body elements to the tail
                topLeft += new MatrixCoords(0, 1);
            }

            this.Head.SetNewPosition(topLeft);
            this.Head.SetNewSpeed(new MatrixCoords(0, 1));

            this.Speed = new MatrixCoords(0, 1);
        }

        private void SnakeDies()
        {
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.CursorVisible = true;
            Console.WriteLine("Game Over! \n");
            Console.WriteLine("Do you want to save your score: [yes/no] \n");
            string input = Console.ReadLine();
            if (input == "yes")
            {
                string inputName;

                do
                {
                    Console.WriteLine("Enter your name:");
                    inputName = Console.ReadLine();
                    if (SaveScore.IsExists(inputName))
                    {
                        Console.WriteLine("Player with that name exist! Please enter another!");
                    }
                } while (SaveScore.IsExists(inputName));

                try
                {
                    SaveScore.SaveScores(inputName, this.SnakeStatus.Points, true);
                    SaveScore.ReadScores(inputName);
                }
                catch (FileLoadException exc)
                {
                    Console.WriteLine(exc.Message);
                }
                catch (FileNotFoundException exc)
                {
                    Console.WriteLine(exc.Message);
                }
                catch (Exception)
                {
                    Environment.Exit(1);
                }
                finally
                {
                    Environment.Exit(2);
                }
            }
            else
            {
                Environment.Exit(0);
            }
        }
    }
}
